package com.yao2san.sim.storage.client.core.uploader;

import com.yao2san.sim.storage.client.util.StorageUtil;
import com.yao2san.sim.storage.client.config.LocalProperties;
import com.yao2san.sim.storage.client.core.enums.StorageType;
import com.yao2san.sim.storage.client.core.event.UploadEvent;
import com.yao2san.sim.storage.client.core.event.UploadResult;
import com.yao2san.sim.storage.client.core.policy.FileRenamePolicy;
import com.yao2san.sim.storage.client.core.policy.FolderPolicy;
import com.yao2san.sim.storage.client.exception.UploadErrorException;
import com.yao2san.sim.storage.client.util.StorageUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.springframework.context.ApplicationContext;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

@Slf4j
public class LocalUploader extends AbstractUploader {

    private final LocalProperties localProperties;

    private final ApplicationContext context;
    private final FolderPolicy folderPolicy;

    private final FileRenamePolicy fileRenamePolicy;

    public LocalUploader(LocalProperties localProperties, ApplicationContext context, FolderPolicy folderPolicy, FileRenamePolicy fileRenamePolicy) {
        this.localProperties = localProperties;
        this.context = context;
        this.folderPolicy = folderPolicy;
        this.fileRenamePolicy = fileRenamePolicy;
    }

    @Override
    public UploadResult upload(InputStream stream, String dest, UploadArgs args) throws IOException, UploadErrorException {
        StorageUtil.checkObjectName(dest);
        final String object = getObject(dest, args);
        String fn = getPathName(object);
        String dir = fn.substring(0, fn.lastIndexOf("/"));
        Path path = Paths.get(dir);
        if (!Files.exists(path)) {
            Files.createDirectories(path);
        }
        File destFile = new File(fn);
        try (FileOutputStream out = new FileOutputStream(destFile)) {
            int size = stream.available();
            IOUtils.copy(stream, out);
            //publish event
            UploadResult result = UploadResult.builder()
                    .object(object)
                    .size((long) size)
                    .url(getUrl(object))
                    .uploadArgs(args)
                    .success(true)
                    .build();
            context.publishEvent(new UploadEvent(result));
            return result;
        } catch (Exception e) {
            //publish event
            UploadResult result = UploadResult.builder()
                    .success(false)
                    .message(e.getMessage())
                    .object(object)
                    .build();
            context.publishEvent(new UploadEvent(result));
            log.error("file upload error", e);
            throw new UploadErrorException(e);
        } finally {
            if (stream != null) {
                stream.close();
            }
        }

    }

    private String getPathName(String object) {
        return StorageUtil.formatPath(this.localProperties.getBucket() + "/" + object);
    }

    /**
     * get unique object
     *
     * @param dest dest
     * @param args args
     * @return object name
     */
    private String getObject(String dest, UploadArgs args) {
        String dir = this.folderPolicy.create();
        String name = FilenameUtils.getName(dest);
        String path = FilenameUtils.getPath(dest);
        if (args != null) {
            FolderPolicy fp = args.getFolderPolicy();
            FileRenamePolicy rp = args.getFileRenamePolicy();
            if (fp != null) {
                dir = fp.create();
            }
            if (rp != null) {
                name = rp.rename(name);
            }
        } else {
            //use default
            dir = folderPolicy.create();
            name = fileRenamePolicy.rename(name);
        }

        String object = getBasePath() + "/" + dir + "/" + path + "/" + name;
        object = StorageUtil.formatPath(object);
        return object;
    }

    private String getUrl(String object) {
        StringBuilder sb = new StringBuilder();
        sb.append("/")
                .append(localProperties.getPrefix())
                .append("/")
                .append(StorageUtil.formatPath(object));
        return localProperties.getEndpoint() + StorageUtil.formatPath(sb.toString());
    }

    @Override
    public String getBasePath() {
        return localProperties.getPath();
    }

    @Override
    public String getName() {
        return StorageType.LOCAL.name();
    }
}
